package org.jg.arkanoid.engine.collider;

import java.util.ArrayList;
import java.util.Collection;

import org.jg.arkanoid.gameobjects.Ball;
import org.jg.arkanoid.gameobjects.GameObject;
import org.jg.arkanoid.gameobjects.MovingObject;
import org.jg.arkanoid.util.JGUtil;
import org.jg.arkanoid.util.Segment;
import org.jg.arkanoid.util.Vector;

public class ObjectCollider 
{
	public static boolean doSpaceBoundsCollision(GameObject gameObject, int spaceXbound, int spaceYbound)
	{
		CollisionEvent collisionEvent = null;
		
		Vector position = gameObject.getPosition();
		double width = gameObject.getWidth();
		double height = gameObject.getHeight();
		
		if(position.getX() + width > spaceXbound)
		{
			collisionEvent = new CollisionEvent(null, new Vector(1,0));
			fixOverlap(gameObject, position.getX() + width - spaceXbound, 0 );
		}
		else if(position.getX() < 0)
		{
			collisionEvent = new CollisionEvent(null, new Vector(-1,0));
			fixOverlap(gameObject, position.getX(), 0 );
		}
		else if(position.getY() + height > spaceYbound)
		{
			collisionEvent = new CollisionEvent(null, new Vector(0,1));
			fixOverlap(gameObject, 0, position.getY() + height - spaceYbound );
		}
		else if(position.getY() < 0)
		{
			collisionEvent = new CollisionEvent(null, new Vector(0,-1));
			fixOverlap(gameObject, 0, position.getY() );
		}
		
		if(collisionEvent != null)
		{
			gameObject.callCollision(collisionEvent);
		}
		
		return collisionEvent != null;
	}
	
	public static boolean doBallToRectangularCollision(Ball ball, GameObject rectangle)
	{
		double go1_x1 = ball.getPosition().getX();
		double go1_y1 = ball.getPosition().getY();
		double go1_x2 = go1_x1 + ball.getWidth();
		double go1_y2 = go1_y1 + ball.getHeight();
		
		double go2_x1 = rectangle.getPosition().getX();
		double go2_y1 = rectangle.getPosition().getY();
		double go2_x2 = go2_x1 + rectangle.getWidth();
		double go2_y2 = go2_y1 + rectangle.getHeight();
		
		if(go1_x1 > go2_x2 || go1_x2 < go2_x1)
			return false;
		
		if(go1_y1 > go2_y2 || go1_y2 < go2_y1)
			return false;
		
		Vector collisionPoint = fixRectangularCollisionOverlap(ball, rectangle);
		Vector collisionVector = getRectangularCollisionVector(ball, rectangle, collisionPoint);
		
		if(collisionVector != null)
		{
			CollisionEvent collisionEvent1 = new CollisionEvent();
			collisionEvent1.setCollisionObject(rectangle);
			collisionEvent1.setCollisionVector(collisionVector);
			
			CollisionEvent collisionEvent2 = new CollisionEvent();
			collisionEvent2.setCollisionObject(rectangle);
			collisionEvent2.setCollisionVector(collisionVector.multiply(-1));
			
			ball.callCollision(collisionEvent1);
			rectangle.callCollision(collisionEvent2);
		}
		
		return true;
	}
	
	public static boolean doRectangularCollision(GameObject gameObject1, GameObject gameObject2)
	{
		
		double go1_x1 = gameObject1.getPosition().getX();
		double go1_y1 = gameObject1.getPosition().getY();
		double go1_x2 = go1_x1 + gameObject1.getWidth();
		double go1_y2 = go1_y1 + gameObject1.getHeight();
		
		double go2_x1 = gameObject2.getPosition().getX();
		double go2_y1 = gameObject2.getPosition().getY();
		double go2_x2 = go2_x1 + gameObject2.getWidth();
		double go2_y2 = go2_y1 + gameObject2.getHeight();
		
		if(go1_x1 > go2_x2 || go1_x2 < go2_x1)
			return false;
		
		if(go1_y1 > go2_y2 || go1_y2 < go2_y1)
			return false;
		
		Vector collisionPoint = fixRectangularCollisionOverlap(gameObject1, gameObject2);
		
		Vector collisionVector = getRectangularCollisionVector(gameObject1, gameObject2, collisionPoint);
		
		CollisionEvent collisionEvent1 = new CollisionEvent();
		collisionEvent1.setCollisionObject(gameObject2);
		collisionEvent1.setCollisionVector(collisionVector);
		
		CollisionEvent collisionEvent2 = new CollisionEvent();
		collisionEvent2.setCollisionObject(gameObject2);
		collisionEvent2.setCollisionVector(collisionVector.multiply(-1));
		
		gameObject1.callCollision(collisionEvent1);
		gameObject2.callCollision(collisionEvent2);
		
		return true;
	}
	
	private static Vector getRectangularCollisionVector(GameObject gameObject1, GameObject gameObject2, Vector collisionPoint)
	{
		Vector collisionVector = new Vector(0,0); 
	
		if(collisionPoint == null)
			return collisionVector;
		
		Double collisionX = collisionPoint.getX();
		Double collisionY = collisionPoint.getY();
		
		Vector position1 = gameObject1.getPosition();
		Vector position2 = gameObject2.getPosition();
		
		// Check vertical collision
		if (collisionX.equals(position1.getX())
				|| collisionX.equals(position1.getX() + gameObject1.getWidth())
				|| collisionX.equals(position2.getX())
				|| collisionX.equals(position2.getX() + gameObject2.getWidth())) {
			collisionVector = new Vector(1, 0);
		}

		// Check horizontal collision
		if (collisionY.equals(position1.getY())
				|| collisionY.equals(position1.getY() + gameObject1.getHeight())
				|| collisionY.equals(position2.getY())
				|| collisionY.equals(position2.getY() + gameObject2.getHeight())) {
			collisionVector = new Vector(0, 1);
		}
		
		return collisionVector;
	}
	
	public static boolean doBallsCollision(Ball ball1, Ball ball2)
	{
		boolean collisionHappened = false;
		
		if(ball1 == null || ball2 == null)
		{
			return collisionHappened;
		}
		
		float ball1Radius = ball1.getDiameter()/2;
		Vector ball1Center = ball1.getPosition().addVector(ball1Radius, ball1Radius);
		
		float ball2Radius = ball2.getDiameter()/2;
		Vector ball2Center = ball2.getPosition().addVector(ball2Radius, ball2Radius);
		
		double overlap = ball1Radius + ball2Radius - JGUtil.pointDistance(ball1Center, ball2Center); 
		if(overlap > 0)
		{
			CollisionEvent collisionEvent1 = new CollisionEvent();
			collisionEvent1.setCollisionObject(ball2);
			collisionEvent1.setCollisionVector(ball2.getVelocity());
			
			CollisionEvent collisionEvent2 = new CollisionEvent();
			collisionEvent2.setCollisionObject(ball1);
			collisionEvent2.setCollisionVector(ball1.getVelocity());
			
			ball1.callCollision(collisionEvent1);
			ball2.callCollision(collisionEvent2);
			
			fixOverlap(ball1, ball2, overlap);
			
			collisionHappened = true;
		}
		
		return collisionHappened;
	}
	
	public static void fixOverlap(GameObject dynamicObject, GameObject staticObject)
	{
		Vector dynamicCentriod = dynamicObject.getCentroid();
		Vector staticCentriod = staticObject.getCentroid();
		
		double x_centerDist = dynamicCentriod.getX() - staticCentriod.getX();
		double y_centerDist = dynamicCentriod.getY() - staticCentriod.getY();
		
		double x_delta = (dynamicObject.getWidth() + staticObject.getWidth())/2 - Math.abs(x_centerDist);
		double y_delta = (dynamicObject.getHeight() + staticObject.getHeight())/2 - Math.abs(y_centerDist);
		
		double x_over = x_delta*Math.signum(x_centerDist);
		double y_over = y_delta*Math.signum(y_centerDist);
		
		if(x_delta > y_delta)
			fixOverlap(dynamicObject, x_over, y_over);
	}
	
	public static void fixOverlap(GameObject gameObject, double x_over, double y_over )
	{
		if(!gameObject.isCollidable())
			return;
		
		gameObject.setPosition( gameObject.getPosition().addVector(-x_over, -y_over) );
	}
	
	public static void fixOverlap(GameObject dynamicObject, GameObject staticObject, double overlap)
	{
		if(overlap <= 0)
			return;
		
		Vector positionShift = new Vector(staticObject.getPosition(), dynamicObject.getPosition()).normalize().multiply(overlap);
		
		dynamicObject.setPosition(dynamicObject.getPosition().addVector(positionShift));
	}
	
	public static Vector fixRectangularCollisionOverlap(GameObject object1, GameObject object2)
	{
		GameObject dynamicObject = null;
		GameObject staticObject = null;
		
		if( ColliderHelper.getObjectsMovedByOverlap().contains(object1.getClass()) )
		{
			dynamicObject = object1;
			staticObject = object2;
		}
		else if( ColliderHelper.getObjectsMovedByOverlap().contains(object2.getClass()) )
		{
			dynamicObject = object2;
			staticObject = object1;
		}
		
		Vector collisionPoint = null;
		
		if(dynamicObject != null && dynamicObject instanceof MovingObject)
			collisionPoint = fixOverlapWithMovingObject( (MovingObject)dynamicObject, staticObject );
		
		return collisionPoint;
	}
	
	public static Vector fixOverlapWithMovingObject(MovingObject movingObject, GameObject staticObject)
	{
		Vector collisionPoint = null;
		
		double mObjDiagLength = Math.hypot(movingObject.getWidth(), movingObject.getHeight());
		Vector mObjVelocity = movingObject.getVelocity();
		
		if(mObjVelocity == null)
			return collisionPoint;
		
		/*Create moving direction vector parallel to velocity with length more then a half of diagonal length 
		(to be sure that this vector starting in the center of rectangle crosses with rectangular's sides)*/
		Vector movingDirection = mObjVelocity.multiply(mObjDiagLength/mObjVelocity.legnth());
		
		Vector movingDirectionStart = movingObject.getCentroid();
		Vector movingDirectionEnd = movingObject.getCentroid().addVector(movingDirection);
		
		Vector mObjCrossPoint = getCollisionHeadPoint(movingObject, staticObject);
		
		if(mObjCrossPoint == null)
		{
			Collection<Vector> mObjCrossPoints = JGUtil.getCrossPoints(new Segment(movingDirectionStart, movingDirectionEnd), JGUtil.getObjectSegments(movingObject), true);
			mObjCrossPoint = JGUtil.getClosestPoint(movingDirectionStart, mObjCrossPoints);
		}
		
		if(mObjCrossPoint == null)
			return collisionPoint;
		
		//Create similar vector that crosses with static object's segments
		double sObjDiagLength = Math.hypot(staticObject.getWidth(), staticObject.getHeight());
		Vector counterMovingDirection = mObjVelocity.multiply(-(mObjDiagLength + sObjDiagLength)/mObjVelocity.legnth());
		
		Vector counterMovingDirectionStart = mObjCrossPoint;
		Vector counterMovingDirectionEnd = mObjCrossPoint.addVector(counterMovingDirection);
		
		Collection<Vector> sObjCrossPoints = JGUtil.getCrossPoints(new Segment(counterMovingDirectionStart, counterMovingDirectionEnd), JGUtil.getObjectSegments(staticObject), false);
//		Vector sObjCrossPoint = JGUtil.getFirst(sObjCrossPoints);
		Vector sObjCrossPoint = JGUtil.getClosestPoint(counterMovingDirectionStart, sObjCrossPoints);
		
		collisionPoint = sObjCrossPoint;
		if(sObjCrossPoint == null)
			return collisionPoint;
		
		double x_shiftDif = sObjCrossPoint.getX() - mObjCrossPoint.getX();
		double y_shiftDif = sObjCrossPoint.getY() - mObjCrossPoint.getY();
		
		//Add 1 to shift to prevent sticking
		x_shiftDif += 1*Math.signum((int)counterMovingDirection.getX());
		y_shiftDif += 1*Math.signum((int)counterMovingDirection.getY());
		
		movingObject.setPosition(movingObject.getPosition().addVector(x_shiftDif, y_shiftDif));
		
		return collisionPoint;
	}
	
	public static Vector getCollisionHeadPoint(MovingObject movingObj, GameObject staticObj)
	{
		Vector northWest = movingObj.getPosition();
		Vector northEast = movingObj.getPosition().addVector(movingObj.getWidth(), 0);
		Vector southWest = movingObj.getPosition().addVector(0, movingObj.getHeight());
		Vector southEast =movingObj.getPosition().addVector(movingObj.getWidth(), movingObj.getHeight());
		
		ArrayList<Vector> possibleCollisionHeadPoints = new ArrayList<Vector>();
		
		double velocityX = movingObj.getVelocity().getX();
		double velocityY = movingObj.getVelocity().getY();
		
		if(velocityX >= 0 && velocityY >= 0) //South-East direction
		{
			possibleCollisionHeadPoints.add(northEast);
			possibleCollisionHeadPoints.add(southEast);
			possibleCollisionHeadPoints.add(southWest);
		}
		else if(velocityX <= 0 && velocityY >= 0) //South-West direction
		{
			possibleCollisionHeadPoints.add(northWest);
			possibleCollisionHeadPoints.add(southEast);
			possibleCollisionHeadPoints.add(southWest);
		}
		else if(velocityX <= 0 && velocityY <= 0) //North-West direction
		{
			possibleCollisionHeadPoints.add(northWest);
			possibleCollisionHeadPoints.add(southWest);
			possibleCollisionHeadPoints.add(southEast);
		}
		else if(velocityX >= 0 && velocityY <= 0) //North-East direction
		{
			possibleCollisionHeadPoints.add(northWest);
			possibleCollisionHeadPoints.add(northEast);
			possibleCollisionHeadPoints.add(southEast);
		}
		
		Vector headPoint = null;
		for(Vector point : possibleCollisionHeadPoints)
		{
			if(JGUtil.rectangleObjectContainsPoint(staticObj, point))
				headPoint = point;
		}
		
		return headPoint;
	}
}
